package com.hj.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.hj.dto.CaptchaDto;
import com.hj.constants.CaptchaConstants.RepCodeEnum;
import com.hj.util.AESUtil;
import com.hj.util.ImageUtils;
import com.hj.util.RandomUtils;
import com.hj.vo.CaptchaVO;
import com.hj.vo.PointVO;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author doudh
 * @date 2020/8/25
 * @description 点选文字验证码
 */
@Service(value = "clickWordCaptchaServiceImpl")
public class ClickWordCaptchaServiceImpl extends AbstractCaptchaService {
    private static Logger logger = LoggerFactory.getLogger(ClickWordCaptchaServiceImpl.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 获取验证码
     *
     * @param captchaVO
     * @author doudh
     * @date 2020/9/9 16:22
     */
    @Override
    public CaptchaDto get(CaptchaVO captchaVO) {
        BufferedImage bufferedImage = ImageUtils.getPicClick();
        if (null == bufferedImage) {
            logger.error("滑动底图未初始化成功，请检查路径");
            return CaptchaDto.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
        }
        CaptchaVO imageData = getImageData(bufferedImage);
        if (imageData == null || StringUtils.isBlank(imageData.getOriginalImageBase64())) {
            return CaptchaDto.errorMsg(RepCodeEnum.API_CAPTCHA_ERROR);
        }
        return CaptchaDto.successData(imageData);
    }

    /**
     * 校验验证码
     *
     * @param captchaVO
     * @author doudh
     * @date 2020/9/9 16:22
     */
    @Override
    public CaptchaDto check(CaptchaVO captchaVO) {
        // 取坐标信息
        String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
        if (!stringRedisTemplate.hasKey(codeKey)) {
            return CaptchaDto.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
        }
        String s = stringRedisTemplate.opsForValue().get(codeKey);
        // 验证码只用一次，即刻失效
        stringRedisTemplate.delete(codeKey);
        List<PointVO> point = null;
        List<PointVO> point1 = null;
        String pointJson = null;
        try {
            point = JSONObject.parseArray(s, PointVO.class);
            // aes解密
            pointJson = decrypt(captchaVO.getPointJson(), point.get(0).getSecretKey());
            point1 = JSONObject.parseArray(pointJson, PointVO.class);
        } catch (Exception e) {
            logger.error("验证码坐标解析失败", e);
            return CaptchaDto.errorMsg(e.getMessage());
        }
        for (int i = 0; i < point.size(); i++) {
            if (point.get(i).x - HAN_ZI_SIZE > point1.get(i).x || point1.get(i).x > point.get(i).x + HAN_ZI_SIZE
                || point.get(i).y - HAN_ZI_SIZE > point1.get(i).y || point1.get(i).y > point.get(i).y + HAN_ZI_SIZE) {
                return CaptchaDto.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
            }
        }
        // 校验成功，将信息存入缓存
        String secretKey = point.get(0).getSecretKey();
        String value = null;
        try {
            value = AESUtil.encrypt(captchaVO.getToken().concat("---").concat(pointJson), secretKey);
        } catch (Exception e) {
            logger.error("AES加密失败", e);
            return CaptchaDto.errorMsg(e.getMessage());
        }
        String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value);
        stringRedisTemplate.opsForValue().set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE, TimeUnit.SECONDS);
        captchaVO.setResult(true);
        return CaptchaDto.successData(captchaVO);
    }

    /**
     * 二次校验
     *
     * @param captchaVO
     * @author doudh
     * @date 2020/9/9 16:23
     */
    @Override
    public CaptchaDto verification(CaptchaVO captchaVO) {
        if (captchaVO == null) {
            return RepCodeEnum.NULL_ERROR.parseError("captchaVO");
        }
        if (StringUtils.isEmpty(captchaVO.getCaptchaVerification())) {
            return RepCodeEnum.NULL_ERROR.parseError("captchaVerification");
        }
        try {
            String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
            if (!stringRedisTemplate.hasKey(codeKey)) {
                return CaptchaDto.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
            }
            // 二次校验取值后，即刻失效
            stringRedisTemplate.delete(codeKey);
        } catch (Exception e) {
            logger.error("验证码坐标解析失败", e);
            return CaptchaDto.errorMsg(e.getMessage());
        }
        return CaptchaDto.success();
    }

    /**
     * 取图片数据
     *
     * @param backgroundImage
     * @author doudh
     * @date 2020/9/9 16:23
     */
    private CaptchaVO getImageData(BufferedImage backgroundImage) {
        CaptchaVO dataVO = new CaptchaVO();
        List<String> wordList = new ArrayList<String>();
        List<PointVO> pointList = new ArrayList();

        Graphics backgroundGraphics = backgroundImage.getGraphics();
        int width = backgroundImage.getWidth();
        int height = backgroundImage.getHeight();

        Font font = new Font(fontType, Font.PLAIN, HAN_ZI_SIZE);
        int wordCount = wordTotalCount;
        // 定义随机1到arr.length某一个字不参与校验
        int num = RandomUtils.getRandomInt(1, wordCount);
        Set<String> currentWords = new HashSet<String>();
        String secretKey = null;
        if (AES_STATUS) {
            secretKey = aesKey;
        }
        for (int i = 0; i < wordCount; i++) {
            String word;
            do {
                word = RandomUtils.getRandomHan(HAN_ZI);
            } while (currentWords.contains(word));
            currentWords.add(word);

            // 随机字体坐标
            PointVO point = randomWordPoint(width, height, i, wordCount);
            point.setSecretKey(secretKey);
            // 随机字体颜色
            if (FONT_COLOR_RANDOM) {
                backgroundGraphics.setColor(new Color(RandomUtils.getRandomInt(1, 255),
                    RandomUtils.getRandomInt(1, 255), RandomUtils.getRandomInt(1, 255)));
            } else {
                backgroundGraphics.setColor(Color.BLACK);
            }
            // 设置角度
            AffineTransform affineTransform = new AffineTransform();
            affineTransform.rotate(Math.toRadians(RandomUtils.getRandomInt(-45, 45)), 0, 0);
            Font rotatedFont = font.deriveFont(affineTransform);
            backgroundGraphics.setFont(rotatedFont);
            backgroundGraphics.drawString(word, point.getX(), point.getY());

            if ((num - 1) != i) {
                wordList.add(word);
                pointList.add(point);
            }
        }

        Font watermark = new Font(waterMarkFont, Font.PLAIN, HAN_ZI_SIZE / 2);
        backgroundGraphics.setFont(watermark);
        backgroundGraphics.setColor(Color.white);
        backgroundGraphics.drawString(waterMark, width - getEnOrChLength(waterMark), height - (HAN_ZI_SIZE / 2) + 30);

        // 创建合并图片
        BufferedImage combinedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics combinedGraphics = combinedImage.getGraphics();
        combinedGraphics.drawImage(backgroundImage, 0, 0, null);

        dataVO.setOriginalImageBase64(ImageUtils.getImageToBase64Str(backgroundImage).replaceAll("\r|\n", ""));
        // pointList信息不传到前端，只做后端check校验
        dataVO.setWordList(wordList);
        dataVO.setToken(UUID.randomUUID().toString().replaceAll("-", ""));
        dataVO.setSecretKey(secretKey);
        // 将坐标信息存入redis中
        String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken());
        stringRedisTemplate.opsForValue().set(codeKey, JSONObject.toJSONString(pointList), EXPIRESIN_SECONDS,
            TimeUnit.SECONDS);
        return dataVO;
    }

    /**
     * 随机字体循环排序下标
     *
     * @param imageWidth
     *            图片宽度
     * @param imageHeight
     *            图片高度
     * @param wordSortIndex
     *            字体循环排序下标(i)
     * @param wordCount
     *            字数量
     * @return
     */
    private PointVO randomWordPoint(int imageWidth, int imageHeight, int wordSortIndex, int wordCount) {
        int avgWidth = imageWidth / (wordCount + 1);
        int x, y;
        if (avgWidth < HAN_ZI_SIZE_HALF) {
            x = RandomUtils.getRandomInt(1 + HAN_ZI_SIZE_HALF, imageWidth);
        } else {
            if (wordSortIndex == 0) {
                x = RandomUtils.getRandomInt(1 + HAN_ZI_SIZE_HALF, avgWidth * (wordSortIndex + 1) - HAN_ZI_SIZE_HALF);
            } else {
                x = RandomUtils.getRandomInt(avgWidth * wordSortIndex + HAN_ZI_SIZE_HALF,
                    avgWidth * (wordSortIndex + 1) - HAN_ZI_SIZE_HALF);
            }
        }
        y = RandomUtils.getRandomInt(HAN_ZI_SIZE, imageHeight - HAN_ZI_SIZE);
        return new PointVO(x, y, null);
    }

}
